<?php
/**
 * Created by PhpStorm.
 * User: Nph
 * Date: 2017/5/30
 * Time: 13:29
 */

namespace apps\api\modules;

use common\map\api\ApiMap;
use common\map\CommonMap;
use common\modules\user\logic\UserLogic;
use common\map\api\ResponseMap;
use common\modules\user\User;
use xing\helper\exception\ApiCodeException;
use xing\helper\exception\ModelYiiException;
use xing\helper\yii\ReturnHelper;
use yii\filters\RateLimiter;
use yii\helpers\Url;
use yii\web\Response;
use Yii;
use yii\base\UserException;
use yii\web\HttpException;
use yii\base\ErrorException;

/**
 * Class ApiBaseController
 * @property int $userId
 * @package api\controllers
 * @property User $user
 */
class ApiBaseController extends \yii\rest\ActiveController
{

    public $modelClass = 'common\modules\site\Region';

    # 会员id，对应User::userId
    public $userId;
    public $user;
    public $checkSign = true;
    public $checkLogin = true;
    public $runTime;
    public $noLoginAction = []; // 不用登陆的方法

    /**
     * @inheritdoc
     */
    public function actions()
    {
        return [
            'docs' => [
                'class' => 'yii2mod\swagger\SwaggerUIRenderer',
                'restUrl' => Url::to(['site/json-schema']),
            ],
            'json-schema' => [
                'class' => 'yii2mod\swagger\OpenAPIRenderer',
                // Тhe list of directories that contains the swagger annotations.
                'scanDir' => [
                    Yii::getAlias('@app/controllers'),
                    Yii::getAlias('@app/models'),
                ],
            ],
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
        ];
    }
    /**
     * @param $action
     * @return bool
     * @throws \Exception
     * @throws \yii\web\BadRequestHttpException
     */
    public function beforeAction ($action) {
        $action->id;
        $this->user = User::getTokenToUser();
        if (Yii::$app->request->get('cx') == '1qjh' && Yii::$app->request->get('userId'))
            $this->user = User::findOne(Yii::$app->request->get('userId'));
        // 如果是等待注册
        if (!empty($this->user) && $this->user->status == User::STATUS_WAIT_REGISTER) {
            $this->user = null;
        }
        $this->userId = $this->user->userId ?? null;
        if (!in_array($action->id, $this->noLoginAction)) {
            // 登陆检查
            if ($this->checkLogin) $this->checkLogin();
        }

        if (!empty($this->user) && $this->user->status != User::STATUS_ACTIVE) {
            ReturnHelper::showJson($this->errorCode(ResponseMap::ERROR_ACCOUNT_EXCEPTION));
        }

        if (!empty($this->user)) User::updateLoginTime($this->userId);
        return parent::beforeAction($action);

    }

    /**
     * @throws ApiCodeException
     * @throws ModelYiiException
     * @throws \Exception
     */
    public function init()
    {
//        \Yii::error('test');
//        $this->runTime = time();
        ReturnHelper::allowOrigin();

        if($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
            exit(ReturnHelper::showJson([]));
        }

        //绑定beforeSend事件，更改数据输出格式
        Yii::$app->getResponse()->on(Response::EVENT_BEFORE_SEND, [$this, 'beforeSend']);
        if (!$this->checkSign(Yii::$app->request->post())) throw new ApiCodeException(ResponseMap::ERROR_SIGN);

    }

    /**
     * 检查登陆状态，如没登陆，将中断程序运行，输出提示信息
     * @throws \Exception
     */
    public function checkLogin()
    {
        if (empty($this->user)) ReturnHelper::showJson($this->errorCode(ResponseMap::ERROR_USER_LOGIN));
    }
    /**
     * @return array
     */
/*    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => CompositeAuth::className(),
            'authMethods' => [
                HttpBasicAuth::className(),
                HttpBearerAuth::className(),
                QueryParamAuth::className(),
            ],
        ];
        return $behaviors;
    }*/


    /**
     * 更改数据输出格式
     * 默认情况下输出Json数据
     * 如果客户端请求时有传递$_GET['callback']参数，输入Jsonp格式
     * 请求正确时数据为  {"success":true,"data":{...}}
     * 请求错误时数据为  {"success":false,"data":{"name":"Not Found","message":"页面未找到。","code":0,"status":404}}
     * @param \yii\base\Event $event
     */
    public function beforeSend($event)
    {
        /* @var $response \yii\web\Response */
        $response = $event->sender;

        /*if ($response->statusCode>=400) {
            //异常处理
            if (true && $exception = Yii::$app->getErrorHandler()->exception) {
                $response->data = $this->convertExceptionToArray($exception);
            }
            //Model出错了
            if ($response->statusCode==422) {
                $messages=[];
                foreach ($response->data as $v) {
                    $messages[] = $v['message'];
                }
                //请求错误时数据为  {"success":false,"data":{"name":"Not Found","message":"页面未找到。","code":0,"status":404}}
                $response->data = [
                    'name'=> 'valide error',
                    'message'=> implode("  ", $messages),
                    'info'=>$response->data
                ];
            }
//            $response->statusCode = 200;
        }*/
        //请求正确时数据为  {"success":true,"data":{...}}

        if (is_array($response->data)) {
            $response->format = Response::FORMAT_JSON;
//            $response->data['wxProVersion'] = CommonMap::$wxProVersion;
        }
        return ;
        $jsonString = $this->getResponsePrepareContent($response);
        # 返回数据签名
        $time = time();
        $md5 = md5($jsonString);
        $sign = md5($md5 . ApiMap::API_SECRET . $time);
        \Yii::$app->getResponse()->getHeaders()->set('Header-sign', $sign);
        \Yii::$app->getResponse()->getHeaders()->set('Header-time', $time);
        //jsonp 格式输出
        if (isset($_GET['callback'])) {
            $response->format = Response::FORMAT_JSONP;
            $response->data = [
                'callback' => $_GET['callback'],
                'data' => $response->data,
            ];
        }
    }

    /**
     * 获取将要发动的流文本
     * @param $response
     * @return mixed
     * @throws \Exception
     */
    private function getResponsePrepareContent(&$response)
    {

        $formatter = $response->formatters[$response->format];
        if (!is_object($formatter)) {
            $response->formatters[$response->format] = $formatter = Yii::createObject($formatter);
        }
        if ($formatter instanceof \yii\web\ResponseFormatterInterface) {
            $formatter->format($response);
        } else {
            throw new \Exception("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
        }
        return $response->content;
    }
    /**
     * 将异常转换为array输出
     * @param $exception
     * @return array
     */
    protected function convertExceptionToArray($exception)
    {
        if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) {
            $exception = new HttpException(500, Yii::t('yii', 'An internal server error occurred.'));
        }
        $array = [
            'name' => ($exception instanceof ErrorException) ? $exception->getName() : 'Exception',
            'message' => $exception->getMessage(),
            'code' => $exception->getCode(),
        ];
        if ($exception instanceof HttpException) {
            $array['status'] = $exception->statusCode;
        }
        if (YII_DEBUG) {
            $array['type'] = get_class($exception);
            if (!$exception instanceof UserException) {
                $array['file'] = $exception->getFile();
                $array['line'] = $exception->getLine();
                $array['stack-trace'] = explode("\n", $exception->getTraceAsString());
                if ($exception instanceof \yii\db\Exception) {
                    $array['error-info'] = $exception->errorInfo;
                }
            }
        }
        if (($prev = $exception->getPrevious()) !== null) {
            $array['previous'] = $this->convertExceptionToArray($prev);
        }
        return $array;
    }

    /**
     * 检查签名
     * @param array $params
     * @return bool
     * @throws \Exception
     */
    protected function checkSign(array $params)
    {
        if (!$this->checkSign || empty($params)) return true;
        # 本地环境可不验签
        if (isset($_REQUEST['cx']) && $_REQUEST['cx'] == '1qjh') return true;
        $sign = $params['sign'] ?? null;
        if (empty($sign)) $this->error('验签缺少sign');

        unset($params['sign']);
        if (empty($params['signTime'])) $this->error('验签缺少signTime');
        if (time() - 120 > $params['signTime']) throw new \Exception('非法');
        ksort($params);

        $string = '';
        foreach ($params as $k => $v) $string .= '&' . $k . '=' . $v;
        $string = trim($string, '&');
        $md5 = md5($string);

        $clientType = Yii::$app->request->post('clientType') ?: Yii::$app->request->get('clientType');
        $secret = $clientType == 'h5' ? ApiMap::API_SECRET_H5 : ApiMap::API_SECRET;
//        er('$string:'.$string . ' ok:' . md5($md5 . $secret));
//        Yii::info('$string:'.$string . ' ok:' . md5($md5 . $secret). '  md5 one='.$md5);
//        Yii::info('$sign: '.$sign.'  check : ' . (md5($md5 . $secret) == $sign));
        return md5($md5 . $secret) == $sign;
    }

    /**
     * 获取JSON并转换为数组
     * @param $postName
     * @return mixed
     */
    protected function postJsonToArray($postName)
    {

        $jsonString = Yii::$app->request->post($postName);
        get_magic_quotes_gpc() && $jsonString = stripslashes($jsonString);
        return json_decode($jsonString, 1);
    }

    /**
     * 执行访问检查
     * @param string $action
     * @param null $model
     * @param array $params
     * @throws \yii\web\ForbiddenHttpException
     */
    public function checkAccess($action, $model = null, $params = [])
    {
        // 检查用户能否访问 $action 和 $model
        // 访问被拒绝应抛出ForbiddenHttpException
        /*if ($action === 'update' || $action === 'delete') {
            if ($model->author_id !== \Yii::$app->user->id)
                throw new \yii\web\ForbiddenHttpException(sprintf('You can only %s articles that you\'ve created.', $action));
        }*/
    }

    public function behaviors()
    {
        $behaviors = parent::behaviors();
        /*$behaviors['authenticatior'] = [
            'class' => QueryParamAuth::className(),
        ];*/
        $behaviors['rateLimiter'] = [
            'class' => RateLimiter::className(),
            'enableRateLimitHeaders' => true,
        ];
        return $behaviors;
    }

    /**
     * 成功返回数据
     * @param $data
     * @param int $code
     * @param string $msg
     * @return mixed
     */
    protected function returnData($data, $code = 0, $msg = '')
    {
//        is_array($data) && (array) $data;
        return ReturnHelper::returnData($data, $msg, 1, $code);
    }


    protected function returnList($list, $msg = '')
    {
//        ReturnHelper::$runTime = time() - $this->runTime;
        return ReturnHelper::returnList($list);
    }

    /**
     * 返回数据
     * @param $return
     * @param string $msg
     * @return array
     */
    protected function returnJson($return, $msg = '')
    {
        return ReturnHelper::returnJson($return, $msg);
    }

    /**
     * 返回统一成功消息
     * @return array
     */
    protected function returnApiSuccess()
    {
        $msg = ResponseMap::$codes[ResponseMap::SUCCESSFUL_OPERATION];
        return ReturnHelper::returnData([], $msg);
    }
    /**
     * 返回数据
     * @param $msg
     * @param $status
     * @param string $code
     * @return array
     */
    protected function returnMsg($msg, $status = 1, $code = null)
    {
        return ReturnHelper::returnData(['message' => $msg], $msg, $status, $code);
    }

    /**
     * 错误码返回错误信息
     * @param $code
     * @param string $msg
     * @return array
     * @throws \Exception
     */
    protected function errorCode($code, $msg = '')
    {
        if (empty($msg) && isset(ResponseMap::$codes[$code])) {
            $msg = ResponseMap::$codes[$code];
        } else {
            \Yii::error('msg:' . $msg . '  code: '. $code, __METHOD__);
        }
        return ReturnHelper::error($msg, $code);
    }

    /**
     * 返回错误信息
     * @param $msg
     * @param int $code
     * @return array
     */
    protected function error($msg, $code = 0)
    {
        \Yii::error('msg:' . $msg . '  code: '. $code, __METHOD__);
        return ReturnHelper::error($msg, $code);
    }

    /**
     * 返回捕获的异常错误
     * @param \Exception $e
     * @param string $category
     * @return array
     * @throws \Exception
     */
    protected function returnExceptionError($e, $category = '')
    {
        $code = $e->getCode();
        $msg = $e->getMessage();
        if (!in_array($code, ResponseMap::$codes)) \Yii::error('msg:' . $msg . '  code: '. $code, $category);
        if (YII_ENV_DEV) throw $e;
        // 判断是否含有中文
        $chinese = preg_replace('/[\w\s]+/isU', '', $msg);
        if (!empty($chinese)) return ReturnHelper::error('系统发生错误', $code);
        if (!empty($code)) return $this->errorCode($code, $msg);
        return ReturnHelper::error($msg, $code);
    }

    /**
     * 检查是不是用户自己的数据
     * @param $action
     * @param null $model
     * @param array $params
     * @throws ApiCodeException
     */
    protected function checkApiUser($action, $model = null, $params = [])
    {
        $fieldUserId = $this->fieldUserId ?? 'userId';
        $userId = $model->{$fieldUserId} ?? null;
        if (!empty($userId)) {
            # 读取api用户
//            $user = User::getTokenToUser();
//            if (empty($user)) throw new ApiCodeException(ResponseMap::ERROR_USER_LOGIN);
            if ($userId != $this->userId) throw new ApiCodeException(ResponseMap::ERROR_POWER_NOT);
        }
    }

    /**
     * api 更新数据
     * @param $post
     * @param $id
     * @param string $scenario
     * @return \yii\db\ActiveRecord
     * @throws ApiCodeException
     * @throws ModelYiiException
     */
    protected function userUpdateData($post, $id, $scenario = '')
    {
        $model = $this->findModel($id);
        # 检查是不是用户自己的数据
        self::checkApiUser('', $model);

        $model->setScenario($scenario ?: $this->updateScenario);
        $model->load($post, '');
        if (!$model->save()) throw new ModelYiiException($model, ResponseMap::ERROR_DATABASE_ERROR);
        return $model;
    }

    /**
     * @param $id
     * @return false|int
     * @throws ApiCodeException
     * @throws \Exception
     * @throws \yii\db\StaleObjectException
     */
    protected function userDeleteData($id)
    {
        $model = $this->findModel($id);
        # 检查是不是用户自己的数据
        self::checkApiUser('', $model);
        return $model->delete();
    }

    /**
     * @param $id
     * @param bool $checkUser
     * @return \yii\db\ActiveRecord
     * @throws ApiCodeException
     */
    protected function view($id, $checkUser = false)
    {

        $model = $this->findModel($id);
        # 检查是不是用户自己的数据
        if ($checkUser) self::checkApiUser('', $model);
        return $model;

    }

    /**
     * @param $id
     * @return \yii\db\ActiveRecord
     * @throws ApiCodeException
     */
    protected function findModel($id)
    {
        $model = self::getModel()->findOne($id);
        if (empty($model)) throw new ApiCodeException(ResponseMap::ERROR_DATA_EMPTY);
        return $model;
    }

    /**
     * @return \yii\db\ActiveRecord
     */
    protected function getModel()
    {
        return new $this->modelClass;
    }
    /**
     * @inheritdoc
     */
    protected function verbs()
    {
        return [
            'index' => ['POST', 'GET', 'HEAD', 'OPTIONS'],
            'view' => ['POST', 'GET', 'OPTIONS'],
            'create' => ['POST', 'OPTIONS'],
            'update' => ['POST', 'OPTIONS'],
            'delete' => ['DELETE', 'GET', 'POST', 'OPTIONS'],
        ];
    }

    /**
     * @param \Exception $e
     * @return array
     */
    protected function exception($e)
    {
        return $this->error($e->getMessage(), $e->getCode());
    }
}